跳到主要内容

MySQL 学习(6)Innodb Double Write

Double Write(两次写)是什么?

如果说 ibuf(Insert Buffer)带给 InnoDB 存储引擎的是性能上的提升,那么 doublewrite(两次写)带来的则是数据页的可靠性。

InnoDB 的 Page Size 一般是 16KB,其数据校验也是针对这 16KB 来计算的,将数据写入到磁盘是以 Page 为单位进行操作的。

我们知道磁盘在写入时,都是以 512 字节为单位,不能保证 MySQL 数据页面 16KB 的一次性原子写。试想,在某个 Dirty Page flush 的过程中,发生了系统断电(或者OS崩溃),16K 的数据只有 8K 被写到磁盘上,只有一部分写是成功的,这种现象被称:部分写失效(partial page write)。

一旦 partial page writes 发生,那么在 InnoDB 恢复时就很尴尬:在 InnoDB 的 Redo Log file(重做日志文件)中虽然知道这个数据页被修改了,但是却无法知道这个页被修改到什么程度,和这个页面相关的 redo 也就无法应用了,也就是说如果出现了偏移量问题,再对进行重做就没有意义了。

重做日志中记录的是对页的物理操作,如偏移量 800,写 aaaa 记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。这就是说,在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 doublewrite。

不需要担心这个 Double Write 导致空间满了,两次写在任何时候记录的都是数据库最后发生改变的若干页面(最多128个),在数据库不断工作的过程中,它会不断的被覆盖,它始终是最新的数据,记录的是修改之后的页面数据,而不是修改之前的数据,它的作用不是还原数据,而是保证不会丢失修改。

double write 的组成部分

1、一部分是 InnoDB 内存中的 double write buffer,大小为 2M;

2、另一部分是物理磁盘上 ibdata 系统表空间中大小为 2MB,共 128 个连续的 Page,既 2 个分区。其中 120 个用于批量写脏,另外 8 个用于 Single Page Flush。做区分的原因是批量刷脏是后台线程做的,不影响前台线程。而 Single page flush 是用户线程发起的,需要尽快的刷脏并替换出一个空闲页出来。

double write 的执行步骤

1、当一系列机制(main 函数触发、checkpoint 等)触发数据缓冲池中的脏页进行刷新到 data file 的时候,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 double write buffer

2、之后通过 double write buffer 再分两次、每次 1MB 顺序写入共享表空间的物理磁盘上。

3、然后马上调用 fsync 函数,同步脏页进磁盘上。由于在这个过程中,double write 页的存储是连续的,因此写入磁盘为顺序写,性能很高;

4、完成 double write 后,再将脏页写入实际的各个表空间文件,这时写入就是离散的了。

各模块协作情况如下图(第一步应为脏页产生的 redo 记录 log buffer,然后 log buffer 写入 redo log file,为简化次要步骤直接连线表示):

补充知识:ibdata 文件

InnoDB 中有共享表空间和独立表空间的概念。共享表空间就是 ibdata1,独立表空间放在每个表的 .ibd(数据和索引)和 .frm(表结构)为后缀的文件中。

共享表空间:

独立表:

单独的表空间只存储该表的数据,索引和插入缓冲的 BITMAP 等信息,其余还放在共享表空间中

ibdata1 里面存储了什么内容?

  • Data dictionary
  • Double write buffer
  • Insert buffer
  • Rollback segments
  • UNDO space
  • Forign key constraint system tables

ibdata1 暴涨的原因?

  • 大量事务,产生大量的 undo log
  • 有旧事物长时间未提交,产生大量旧 undo log
  • file I/O 性能差,purge 进展慢
  • 32bit系统下bug

double write 还原数据

在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 doublewrite。

double write 总结:

1、两次写在任何时候记录的都是数据库最后发生改变的若干页面(最多128个),在数据库不断工作的过程中,它会不断的被覆盖,它始终是最新的数据,记录的是修改之后的页面数据,而不是修改之前的数据,它的作用不是还原数据,而是保证不会丢失修改。

2、至于性能问题,表面看上去,它是每一个页面都写了 2 遍,则会非常影响性能,但实际上,由于将所写的页面都先缓存到内存中,到达 128 个之后才真正写入,那么对于磁盘而言,连续写与分散写(每个页面自己写)的性能相差很大的,而两次写正是将一个簇数量的页面组合起来形成 2 个连续的空间 写入到两次写空间中,有效的利用这了这特点,所以性能是不会相差 1 倍的。实际上经过测试,可能两次写使得性能降低了 10%。

3、有其它一些数据库完全没有类似 2 次写的问题,比如达梦等,这个主要是由于它们采用了全物理的 REDO,将一个页面的写操作都拆成一个个的小的物理写入,这种情况下就不会存在写断裂的情况,因为不管怎么写,日志都是对一个页面操作的重演,在 REDO 做完之后,页面的状态肯定是正确的。